package app

import (
	"fmt"
	"sync"

	_ "github.com/go-sql-driver/mysql"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
	"xorm.io/xorm"
	xormlog "xorm.io/xorm/log"
)

var engine *xorm.Engine
var (
	loggerMap = map[string]*zap.Logger{}
	loggerMu  = &sync.Mutex{}
)

func GetDbEngine() *xorm.Engine {
	config := Config()
	dataSourceName := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8",
		config.MySQL.User,
		config.MySQL.Password,
		config.MySQL.Host,
		config.MySQL.Port,
		config.MySQL.Database,
	)

	var err error
	engine, err = xorm.NewEngine("mysql", dataSourceName)

	// 连接池中最大连接数
	engine.SetMaxOpenConns(100)
	// 连接池中最大空闲连接数
	engine.SetMaxIdleConns(10)
	// 单个连接最大存活时间(单位:秒)
	engine.SetConnMaxLifetime(10)
	// engine.ShowSQL(true)

	// 输出日志 终点部分使用自定义日志记录器
	engine.SetLogger(&customXormLogger{
		level:   xormlog.LOG_INFO,
		showSQL: true,
	})

	if err != nil {
		panic("MySQL 连接失败：" + err.Error())
	}

	return engine
}

// zap
func getZapLog() *zap.Logger {
	loggerName := "db" // 日志文件名称

	if logger, has := loggerMap[loggerName]; has {
		return logger
	} else {
		loggerMu.Lock()

		output := zapcore.AddSync(&lumberjack.Logger{
			Filename:   "./db.log",
			MaxSize:    1,  // 日志文件最大容量(单位：MB),超过容量，文件会自动分割
			MaxBackups: 1,  // 保留的日志天数(单位：天)
			MaxAge:     10, // 保留的日志文件个数，文件数量超过该值，最旧的文件会被删除
		})

		// 如果需要可以输出当控制台
		//output = zapcore.NewMultiWriteSyncer(output, os.Stdout)
		level := zapcore.DebugLevel
		_ = level.Set("info") // 设置日志级别  debug info warn error fatal (日志级别从大到小)

		core := zapcore.NewCore(
			zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), // 设置日志输出格式 这块可以自定义
			output,
			zap.NewAtomicLevelAt(level),
		)
		newLogger := zap.New(core, zap.AddStacktrace(zapcore.ErrorLevel))

		loggerMap[loggerName] = newLogger
		loggerMu.Unlock()
		return newLogger
	}
}

// 重点：实现xormLogger接口 自定义记录器
type customXormLogger struct {
	level   xormlog.LogLevel
	showSQL bool
}

var _ xormlog.Logger = &customXormLogger{}

func (c *customXormLogger) Debug(v ...interface{}) {
	if c.level <= xormlog.LOG_DEBUG {
		fmt.Println(v...)
	}
}

func (c *customXormLogger) Debugf(format string, v ...interface{}) {
	if c.level <= xormlog.LOG_DEBUG {
		getZapLog().Debug(fmt.Sprint(v...))
	}
}

func (c *customXormLogger) Error(v ...interface{}) {
	if c.level <= xormlog.LOG_ERR {
		fmt.Println(v...)
	}
}

func (c *customXormLogger) Errorf(format string, v ...interface{}) {
	if c.level <= xormlog.LOG_ERR {
		getZapLog().Error(fmt.Sprint(v...))
	}
}

func (c *customXormLogger) Info(v ...interface{}) {
	if c.level <= xormlog.LOG_INFO {
		fmt.Println(v...)
	}
}

func (c *customXormLogger) Infof(format string, v ...interface{}) {
	if c.level <= xormlog.LOG_INFO {
		getZapLog().Info(fmt.Sprint(v...))
	}
}

func (c *customXormLogger) Warn(v ...interface{}) {
	if c.level <= xormlog.LOG_WARNING {
		fmt.Println(v...)
	}
}

func (c *customXormLogger) Warnf(format string, v ...interface{}) {
	if c.level <= xormlog.LOG_WARNING {
		getZapLog().Warn(fmt.Sprint(v...))
	}
}

func (c *customXormLogger) Level() xormlog.LogLevel {
	return c.level
}

func (c *customXormLogger) SetLevel(l xormlog.LogLevel) {
	c.level = l
}

func (c *customXormLogger) ShowSQL(show ...bool) {
	if len(show) == 0 {
		c.showSQL = true
		return
	}
	c.showSQL = show[0]
}

func (c *customXormLogger) IsShowSQL() bool {
	return c.showSQL
}
